import subprocess,os,re,time
import cv2
import xml.etree.ElementTree as ET
import logging
'''
工具介绍：
    本工具由抖音：像素飞行员 开发，请勿用于违法犯罪。termux通过运行python3控制adb实现。
封装了以下功能：日志输出 找字点击（节点、OCR）找图（opencv）打开APP 切换APP 关闭APP 重启APP 
点击屏幕 删除缓存 输入文本 获取剪贴板 打开url 拍照 发短信 获取短信 打电话 播放音乐
开关wifi 开关蓝牙 开关移动数据 熄屏解锁等。

优点：功能丰富、容易拓展、可以把代码烧录到开发板，usb控制手机
缺点：不适合零基础小白
    
项目声明：

    这是一个非盈利的项目，主打一个简单易懂。请勿用于商用，工具开完全免费。不同的手机adb可能有差异，
    如果不能使用或者异常请自己调整！拒绝无障碍！邪恶的资本家! 消耗性能打广告!
    
增加功能：

    方法一：创建新类，继承Tools类
    方法二：直接新增方法 def name(self,参数1,可选参数.....)
    
注意事项：

    开发者模式和运行模式有区别！开发者模式需要把文件拉回项目本地，运行模式直接读写。

'''
class Tools:
    def __init__(self,mode=False,enable_logging=True,description=None,save='/sdcard/ui.xml'):
        # 开发模式
        self.mode=mode
        # 日志初始化
        self.enable_logging=enable_logging
        if self.enable_logging:
            logging.basicConfig(level=logging.DEBUG)
        # 描述
        self.description=description
        # 缓存位置
        self.save=save
        
    # 使用算法把坐标范围 字符串 转换成中心坐标
    def get_text_coordinates(self,string):
        numbers = []
        start_index = string.find("[") + 1
        while start_index > 0:
            end_index = string.find("]", start_index)
            if end_index > 0:
                number_string = string[start_index:end_index]
                numbers.extend(number_string.split(","))
            start_index = string.find("[", end_index) + 1
        numbers = [int(num) for num in numbers]
        new_numbers = [(numbers[0] + numbers[2]) / 2, (numbers[1] + numbers[3]) / 2]
        return new_numbers
    
    # 点击屏幕
    def click(self,x,y):
        adb_command = f"adb shell input tap {x} {y}"
        subprocess.run(adb_command, shell=True)
        logging.debug(f'点击坐标了：({x},{y})')
    
    #获取当前界面的节点信息，并将节点信息默认保存在临时目录：/sdcard/ui.xml 
    def get_all_node(self):
        subprocess.run(['adb', 'shell', 'uiautomator', 'dump', self.save])
        if self.mode==True:
            subprocess.run(['adb', 'pull', '/sdcard/ui.xml'])
            logging.debug('开发模式，已经将节点文件传回！')
        else:
            logging.debug('运行模式，获取当前界面节点成功！')
        
    # 根据文本查询text的坐标范围(完全匹配) 节点如果找不到，请用ocr
    def node_text_find(self,text1,do=False):
        Tools.get_all_node(self)
        with open('ui.xml', 'r', encoding='utf-8') as file:
            xml_dump = file.read()
            root = ET.fromstring(xml_dump)
            for element in root.iter('node'):
                logging.debug('开始扫描！')
                text = element.attrib.get('text')
                if text==text1:
                    bounds1 = element.attrib.get('bounds')
                    logging.info(f'找到了返回=>Text: {text}, Bounds: {bounds1}')
                    if do==False:
                        return bounds1
                    elif do==True:
                        x=Tools.get_text_coordinates(self,bounds1)[0]
                        y=Tools.get_text_coordinates(self,bounds1)[1]
                        logging.info(f'找到了文本返回=>:{text}中心坐标为：({x},{y})')
                        return x,y
                else:
                    logging.warning('还没有找到文本！end！')
    
    # ocr文字识别
                   
    # 删除缓存 
    def del_tmp(self):
        subprocess.run(['adb', 'shell', 'rm', self.save])
        logging.info('删除缓存成功！')
        
    #截图封装
    def get_image(self,name='screenshot1.png'):
        logging.debug('开始截屏！')
        subprocess.run(f'adb shell screencap -p /sdcard/{name}', shell=True)
        logging.info('截图成功')
        if self.mode:
            filename=f'/sdcard/{name}'
            subprocess.run(['adb', 'pull', filename])
            logging.debug('开发模式，将截屏复制到本地')
        
    # 图片识别/do为true则调用截图；调用了cv2用大图（屏幕）和小图（需要找到图）进行识别
    # 如果要丰富图像识别：验证码识别、滑动/旋转，自行增加
    def find_img(self,big_image_path, small_image_path,do=False):
        logging.info('开始图片识别！')  
        
        if do==True:
            Tools.get_image(self)
           
        big_image = cv2.imread(big_image_path)
        small_image = cv2.imread(small_image_path)

        result = cv2.matchTemplate(big_image, small_image, cv2.TM_CCOEFF_NORMED)
        _, _, min_loc, max_loc = cv2.minMaxLoc(result)

        small_image_width, small_image_height = small_image.shape[:2]
        top_left = min_loc
        center = (top_left[0] + small_image_width // 2, top_left[1] + small_image_height // 2)
        bottom_right = (top_left[0] + small_image_width, top_left[1] + small_image_height)
        logging.info("小图在大图上的坐标范围：", top_left, bottom_right)
        logging.debug(f'中心坐标为：{center}')
        return center[0],center[1]
          
    # 通过包名打开APP 可能会失败
    def open_app_by_package(self,package_name):
        os.system(f'adb shell am start -n {package_name}/.MainActivity')
        logging.info(f'{package_name}打开成功！')
        
    # 通过包名关闭APP
    def close_app_by_package(self,package_name):
        os.system(f'adb shell am force-stop {package_name}')
        logging.info(f'{package_name}关闭成功！')
    
    # 获取前后台运行的程序包名
    def get_running_apps_packages(self):
        adb_cmd = 'adb shell dumpsys activity activities'
        result = subprocess.run(adb_cmd, capture_output=True, text=True, shell=True)
        output = result.stdout
        # 解析输出，获取后台和前台运行的应用程序
        background_apps = []
        foreground_apps = []
        lines = output.splitlines()
        for line in lines:
            line = line.strip()
            if line.startswith('Running activities') or line.startswith('Run #'):
                continue
            if line.startswith('Running'):
                foreground_apps.append(re.findall(r"(\w+\.\w+/\w+)", line)[0])
            else:
                match = re.findall(r"(\w+\.\w+/\w+)", line)
                if match:
                    background_apps.append(match[0])
        logging.info(f"前台运行的应用程序包名: {background_apps[0]}")
        logging.info(f"后台运行的应用程序包名: {background_apps}")
        return foreground_apps, background_apps
  
    # 将后台程序切换到前台 （待调试）
    def bring_app_to_foreground_by_package(self,package_name):
        adb_cmd = f'adb shell am broadcast -a {package_name}.ACTION_FORCE_STOP'
        subprocess.run(adb_cmd, shell=True)
        logging.info(f'已经将{package_name}切换到前台！')

    # 重启应用 重启可能会失败，请自行检查替换启动事件
    def restart_app_by_package(self,package_name):
        subprocess.run(f'adb shell am force-stop {package_name} ', shell=True)
        logging.info(f'关闭{package_name}中！')
        time.sleep(1)
        os.system(f'adb shell am start -n {package_name}/.MainActivity')
        logging.info(f'{package_name}打开成功！')
    
    # 操作APP的其它方法（待更新）
    
    # activity控制APP内部的方法（待更新）
    
    # 输入文本
    def input_text(self,text):
        adb_cmd = f'adb shell input text "{text}"'
        subprocess.run(adb_cmd, shell=True)
        
    # 获取剪贴板内容（待测试）
    def get_clipboard(self):
        result = subprocess.check_output('adb shell am broadcast -a clipper.get', shell=True)
        # 将输出转换为字符串并按行分割存储到列表中
        clipboard_content = result.decode().strip().split('\n')
        # 打印剪贴板内容列表
        logging.info("剪贴板内容：", clipboard_content)
        return clipboard_content
    
    # 监听消息通知 
    
    # 打开url > 配合其它功能使用:识别文字 点击
    def open_url(self,url):
       subprocess.run(f'adb shell am start -a android.intent.action.VIEW -d "{url}"', shell=True)          
       
    # 拍照
    def start_camera(self,front_camera=True,local_path='/sdcard/123.jpg',do=True):
        camera_id = '0' if front_camera else '1'
        adb_cmd = f'adb shell am start -a android.media.action.IMAGE_CAPTURE --ei android.intent.extras.CAMERA_FACING {camera_id}'
        subprocess.run(adb_cmd, shell=True)
        if do==True:
            adb_cmd = f'adb pull /sdcard/DCIM/Camera/photo.jpg {local_path}'
            subprocess.run(adb_cmd, shell=True)
    
    # 播放音乐   
    def play_music(self,file_path):
        adb_cmd = f'adb shell am start -a android.intent.action.VIEW -d file://{file_path} -t audio/*'
        subprocess.run(adb_cmd, shell=True)
        
    # 获取短信
    def get_sms(self):
        adb_cmd = 'adb shell content query --uri content://sms/inbox'
        result = subprocess.run(adb_cmd, capture_output=True, text=True, shell=True)
        output = result.stdout

        sms_dict = {}
        sms_pattern = r'_id=(\d+).*?address=(.*?) .*?body=(.*?)\s+read'
        matches = re.findall(sms_pattern, output, re.DOTALL)
        for match in matches:
            sms_id = match[0]
            sender = match[1]
            message = match[2]
            sms_dict[sms_id] = {'sender': sender, 'message': message}

        return sms_dict
    
    # 发送短信 手机号 短信信息 卡槽
    def send_sms(self,phone_number, message, sim_slot):
        adb_cmd = f'adb shell am start -a android.intent.action.SENDTO -d sms:{phone_number} --es sms_body "{message}" --euicc {sim_slot}'
        subprocess.run(adb_cmd, shell=True)
    
    # 开关WIFI
    def toggle_wifi(self,enable):
        if enable:
            subprocess.run(["adb", "shell", "svc", "wifi", "enable"])
            logging.info("已启用WIFI")
        else:
            subprocess.run(["adb", "shell", "svc", "wifi", "disable"])
            logging.info("已禁用WIFI")
            
    # 开关蓝牙(待测试)
    def toggle_bluetooth(self,enable):
        if enable:
            subprocess.run('adb shell service call bluetooth_manager 6', shell=True)
        else:
            subprocess.run('adb shell service call bluetooth_manager 8', shell=True)
            
    #开关流量数据
    def toggle_data(self,enable):
        if enable:
            subprocess.run('adb shell svc data enable', shell=True)
        else:
            subprocess.run('adb shell svc data disable', shell=True)
   
    # gps开关
      
    # 熄屏解锁（待测试）
    def unlock_device(self,password):
        subprocess.run('adb shell input keyevent KEYCODE_POWER', shell=True)
        subprocess.run(f'adb shell input text {password}', shell=True)
        subprocess.run('adb shell input keyevent KEYCODE_ENTER', shell=True)
        
    # 拨号
    
    # 发邮邮件
    
    # 扫描相册
    
    # 扫描病毒、木马
    
    # 基于termux的gui（光标界面）
    
    # 基于argpase命令行工具的封装
    
    # 基于root的操作：读写内存、解密/恢复APP数据
    
    # 烧录开发板
    
    